<?php

namespace App\Ad\Postback\Handle;

use App\Ad\Docking\DockingBase;
use App\ClickHouse\AdCostService;
use App\Commons\CommonConstant;
use App\Datalog\Events\AdPayPostbackEventLog;
use App\Models\AdMonitor;
use App\Models\AdPlan;
use App\Models\AdPostbackConfig;
use App\Models\FirstupAd;
use App\Models\Log\LogAdPostback;
use App\Models\Log\LogAdPostbackRegpay;
use App\Models\Orders;
use App\Models\UserExtend;
use App\Services\AdPostbackRegpayService;
use App\Services\OrdersCallClientService;
use App\Structs\AdPayPostbackStruct;

// 付费回传
class PayPostback extends PostbackBase
{
    protected $plan;
    protected $order;
    protected $postbackConfig;
    protected $userExtend;

    public function __construct(LogAdPostback $log)
    {
        parent::__construct($log);
        $this->plan = AdPlan::find($log->plan_id);
        if ($this->plan->postback_config_id > 0) {
            $this->postbackConfig = AdPostbackConfig::find($this->plan->postback_config_id);
        }
        $this->order = Orders::find($log->mod_id);
    }

    private function getUserExtend()
    {
        if (empty($this->userExtend)) {
            $this->userExtend = UserExtend::where('user_id', $this->order->user_id)->first();
        }

        return $this->userExtend;
    }

    private function postByApi($call_money)
    {
        $log = $this->log;
        $firstup_ad = FirstupAd::find($log->firstup_ad_id);
        $monitor    = AdMonitor::find($firstup_ad->monitor_id);
        if (!$monitor) {
            $monitor = new AdMonitor;
        }

        $docking = DockingBase::getClass($log->media_docking_id);
        $order = $this->order;
        $order->call_money = $call_money;
        try {
            $status = $docking->postbackToMedia(CommonConstant::EVENT_PAY, $monitor, $firstup_ad, $order);
        } catch (\Throwable $th) {
            $status = LogAdPostback::STATUS_ERROR;
        }

        $log->post_money = $call_money;
        $this->updateStatus($status);
        $this->updateCheckNum(true);

        // 上报成功落地日志
        if ($status == LogAdPostback::STATUS_SUCC) {
            AdPayPostbackEventLog::getInstance()->addLog($order->user_id, $log);
        }

        return true;
    }

    private function postBySdk($call_money)
    {
        $log = $this->log;
        $order = $this->order;

        // 把需要上报的订单和金额放入到 orders_call_client
        OrdersCallClientService::getInstance()->addCall($order, $call_money);

        $log->post_money = $call_money;
        $this->updateStatus(LogAdPostback::STATUS_SDK_WAIT_CALL);
        $this->updateCheckNum(true);

        return true;
    }

    // sdk订单通知给前端后，更新回传日志状态
    // 此接口由外部调用
    public function postSuccessBySdk()
    {
        $log = $this->log;
        $order = $this->order;

        if ($log->status != LogAdPostback::STATUS_SDK_WAIT_CALL) {
            return;
        }

        $this->updateStatus(LogAdPostback::STATUS_SUCC, true);

        // 上报成功落地日志
        AdPayPostbackEventLog::getInstance()->addLog($order->user_id, $log);

        return true;
    }

    public function handle()
    {
        // 处理并获取最终需要回传的金额
        $call_money = $this->handleCallMoney();
        if (!is_numeric($call_money)) {
            $this->updateCheckNum(true);
            return;
        }

        // 测试
        // $this->log->post_money = $call_money;
        // $this->updateStatus(LogAdPostback::STATUS_SUCC);
        // $this->updateCheckNum(true);
        // return;

        if ($this->plan->access_type == AdPlan::ACCESS_TYPE_API) {
            $this->postByApi($call_money);
        } else {
            $this->postBySdk($call_money);
        }

        return true;
    }

    // 处理并获取最终需要回传的金额
    // 1、判断订单类型
    // 2、处理卡 付费用户(新)
    // 3、根据配置判断回传金额
    // 4、判断roi是否已经达标
    public function handleCallMoney()
    {
        // 1、订单类型
        $notify = $this->checkOrderType();
        if ($notify->status !== LogAdPostback::STATUS_NORMAL) {
            $this->updateStatus($notify->status);
            return;
        }

        // 2、付费用户(新)
        $notify = $this->checkRegPay();
        if ($notify->status !== LogAdPostback::STATUS_NORMAL) {
            $this->updateStatus($notify->status);
            return;
        }

        // 3、回传金额
        $notify = $this->getMoneyByOrderConfig();
        // 如果已判定为概率不回传，则返回
        if ($notify->status !== LogAdPostback::STATUS_NORMAL) {
            $this->updateStatus($notify->status);
            return;
        }
        $rate_money = $notify->callAmonut;

        // 最终要回传的金额
        $call_money = $rate_money;

        // 4、roi期望回传金额
        $notify = $this->getRoiPostMoney();
        $expect_money = $notify->callAmonut;
        if ($expect_money > 0) {
            // 如果期望回传金额大于实际金额，则回传实际金额
            if ($expect_money > $rate_money) {
                $call_money = $rate_money;
            } else {
                $call_money = $expect_money;
            }
        }

        // 如果roi已达预期，则判断是否继续回传
        if ($notify->status == LogAdPostback::STATUS_MONEY_ROI_OK) {
            $notify = $this->getRoiPostMoneyKeep();
            if ($notify->status !== LogAdPostback::STATUS_NORMAL) {
                $this->updateStatus($notify->status);
                return;
            }

            // 固定回传金额
            if ($notify->roiokMoneyLock) {
                $call_money = $notify->roiokMoney;
            }
        }

        // yqlog(['计划id'=>$this->plan->id, '回传金额'=>$rate_money, 'roi期待金额'=>$expect_money, '最终金额'=>$call_money]);
        return $call_money;
    }

    // 检测回传订单类型
    public function checkOrderType(): AdPayPostbackStruct
    {
        $notify = new AdPayPostbackStruct();

        $order = $this->order;
        $plan = $this->plan;

        // 订单类型
        $goods_type_list = json_decode($plan->postback_goods_type, true);
        if (!empty($goods_type_list)) {
            // 订单类型不匹配无需上报
            if (!in_array($order->goods_type, $goods_type_list)) {
                $notify->status = LogAdPostback::STATUS_ORDER_TYPE;
                return $notify;
            }
        }

        return $notify;
    }

    // 检测回传方式
    public function checkPostOrderTypes(): AdPayPostbackStruct
    {
        $notify = new AdPayPostbackStruct();
        if (!$this->postbackConfig) {
            return $notify;
        }

        $post_type = $this->postbackConfig->orders_type;

        // 全部回传
        if ($post_type == AdPostbackConfig::POST_ORDER_TYPE_ALL) {
            return $notify;
        }

        // 只回传首冲
        if ($post_type == AdPostbackConfig::POST_ORDER_TYPE_FIRST) {
            $user_exetend = $this->getUserExtend();
            if (!$user_exetend) {
                return $notify;
            }
            if ($this->order->create_time != $user_exetend->first_pay_time) {
                $notify->status = LogAdPostback::STATUS_ORDER_TYPE_FIRST;
                return $notify;
            }
        }

        return $notify;
    }

    // 获取订单当天用户累计付费金额和次数
    private function getTodayPayInfo()
    {
        $stime = strtotime(date('Y-m-d', $this->order->create_time));
        $etime = $stime+86400;

        $one = Orders::selectRaw("count(1) as num, sum(money) as money")
            ->whereRaw("create_time>={$stime} AND create_time<{$etime}")
            ->where('user_id', $this->order->user_id)
            ->whereIn('status', [1,2,3])
            ->first();

        return $one;
    }

    // 是否 付费用户(新)
    private function orderIsRegPay()
    {
        $order_day = strtotime(date('Y-m-d', $this->order->create_time));
        $reg_day = strtotime(date('Y-m-d', $this->log->reg_time));
        if ($order_day != $reg_day) {
            return false;
        }

        return true;
    }

    // 首日是否完成过付费
    private function hasRegPayUser()
    {
        $user_exetend = $this->getUserExtend();
        $first_pay_day = ceil(($user_exetend->first_pay_time - $user_exetend->create_time)/86400);
        if ($first_pay_day == 1) {
            return true;
        }

        return false;
    }

    // 判断此订单配置是否符合命中
    // 判断金额范围
    // 判断用户类型和付费次数
    private function checkOrderConfigHit($item): AdPayPostbackStruct
    {
        $notify = new AdPayPostbackStruct();
        $money = $this->order->money;

        // 订单金额判断
        if (empty($item['max'])) {
            $notify->status = LogAdPostback::STATUS_MONEY_RATE;
            return $notify;
        }

        if ($money < $item['min'] || $money > $item['max']) {
            $notify->status = LogAdPostback::STATUS_MONEY_RATE;
            return $notify;
        }

        if ($item['min'] == $item['max'] && $money != $item['min']) {
            $notify->status = LogAdPostback::STATUS_MONEY_RATE;
            return $notify;
        }

        // 注册天数
        $reg_min = $item['reg_min'] ?? 0;
        $reg_max = $item['reg_max'] ?? 10000;
        $user_exetend = $this->getUserExtend();
        $reg_day = ceil((time() - $user_exetend->create_time)/86400);

        // 判断用户类型和付费次数
        $user_type = $item['user_type'] ?? 0;
        $pay_min = $item['pay_min'] ?? 0;
        $pay_max = $item['pay_max'] ?? 10000;

        // 付费用户
        if ($user_type == AdPostbackConfig::USER_TYPE_PAY) {
            $todaypay = $this->getTodayPayInfo();
            if (!$todaypay || $todaypay->num < $pay_min || $todaypay->num > $pay_max) {
                $notify->status = LogAdPostback::STATUS_MONEY_PAY_NUM;
                return $notify;
            }
            if ($reg_day < $reg_min || $reg_day > $reg_max) {
                $notify->status = LogAdPostback::STATUS_REG_DAY;
                return $notify;
            }
        }
        // 付费用户(新)
        if ($user_type == AdPostbackConfig::USER_TYPE_REG_PAY) {
            if (!$this->orderIsRegPay()) {
                $notify->status = LogAdPostback::STATUS_MONEY_PAY_REG;
                return $notify;
            }
            $todaypay = $this->getTodayPayInfo();
            if (!$todaypay || $todaypay->num < $pay_min || $todaypay->num > $pay_max) {
                $notify->status = LogAdPostback::STATUS_MONEY_PAY_NUM;
                return $notify;
            }
        }

        $notify->status = LogAdPostback::STATUS_NORMAL;
        return $notify;
    }


    // 根据金额控制返回金额
    private function getMoneyByOrderConfig(): AdPayPostbackStruct
    {
        $notify = new AdPayPostbackStruct();
        $money = $this->order->money;
        $notify->callAmonut = $money;

        // 如果没有配置则返回原价格
        if (!$this->postbackConfig) {
            return $notify;
        }

        $orders_config = json_decode($this->postbackConfig->orders_config, true);

        $item = [];
        foreach ($orders_config as $val) {
            $ret_notify = $this->checkOrderConfigHit($val);
            if ($ret_notify->status === LogAdPostback::STATUS_NORMAL) {
                $item = $val;
                break;
            }
        }

        if (empty($item)) {
            return $notify;
        }

        // 回传概率判断
        $rand_num = mt_rand(1, 100);
        $rate = $item['rate'] ?? 0;
        if ($rand_num > $rate) {
            $notify->status = LogAdPostback::STATUS_MONEY_RATE;
            return $notify;
        }

        // 回传方式
        $type = $item['type'] ?? AdPostbackConfig::MONEY_TYPE_PER;
        switch ($type) {
            // 按比例回传
            case AdPostbackConfig::MONEY_TYPE_PER:
                $money_rate = $item['money_rate'] ?? 100;
                $money = round($money*$money_rate/100, 2);
                break;
            // 按固定金额回传
            case AdPostbackConfig::MONEY_TYPE_GUDING:
                $money_guding = $item['money_guding'] ?? 0;
                $money = $money_guding;
                break;
        }

        // 取整金额回传 不足1元按1元，超过1元向下取整
        // 此配置通常用于sdk回传
        $floor = $item['floor'] ?? 0;
        if ($floor == 1) {
            if ($money < 1) {
                $money = 1;
            } else {
                $money = floor($money);
            }
        }
        $notify->callAmonut = $money;

        return $notify;
    }

    // 获得当天注册并付费的人数和金额
    private function getTodayRegPayMoney($type)
    {
        $stime = strtotime(date('Y-m-d', $this->order->create_time));
        $etime = $stime+86400;
        $plan_id = $this->plan->id;

        // 粒度
        $wheres = "1=1";
        switch ($type) {
            // 按计划粒度
            case AdPostbackConfig::ROI_TYPE_PLAN:
                break;
            // 按广告组粒度
            case AdPostbackConfig::ROI_TYPE_ADGROUP:
                $val = $this->log->myad_group_id;
                $wheres = "myad_group_id='{$val}'";
                break;
            // 按广告粒度
            case AdPostbackConfig::ROI_TYPE_AD:
                $val = $this->log->myad_ad_id;
                $wheres = "myad_ad_id='{$val}'";
                break;
        }

        $one = LogAdPostback::selectRaw("sum(DISTINCT user_id) as usernum, sum(post_money) as money")
            ->whereRaw("convert_time >= {$stime} AND convert_time < {$etime}")
            ->whereRaw("plan_id={$plan_id}")
            ->whereRaw($wheres)
            ->whereRaw("from_unixtime(convert_time, '%Y%m%d')=from_unixtime(reg_time, '%Y%m%d')")
            ->first();

        return $one;
    }

    // 获取当天的媒体消耗
    private function getTodayCost()
    {
        $stime = strtotime(date('Y-m-d', $this->order->create_time));
        $etime = $stime+86400;
        $plan_id = $this->plan->id;

        // 请求参数
        $params = [
            'stime'   => $stime,
            'etime'   => $etime,
            'plan_id' => $plan_id,
        ];

        // roi粒度
        $roi_type = $this->postbackConfig->roi_type;
        switch ($roi_type) {
            // 按计划粒度
            case 1:
                break;
            // 按广告组粒度
            case 2:
                $params['myad_group_id'] = $this->log->myad_group_id;
                break;
            // 按广告粒度
            case 3:
                $params['myad_ad_id'] = $this->log->myad_ad_id;
                break;
        }

        // 前往ck获取消耗数据
        $allcost = AdCostService::getInstance()->getTodayCost($params);

        return $allcost;
    }

    // 获得当天目前期待上报的金额
    public function getRoiPostMoney(): AdPayPostbackStruct
    {
        $notify = new AdPayPostbackStruct();

        if (!$this->postbackConfig) {
            return $notify;
        }

        // 不需要roi控制
        $roi_per = $this->postbackConfig->roi_per;
        if ($roi_per == '0' || $roi_per == '100') {
            return $notify;
        }

        $cost = $this->getTodayCost();
        if ($cost === 0) {
            $notify->status = LogAdPostback::STATUS_MONEY_ROI_OK;
            return $notify;
        }

        $item = $this->getTodayRegPayMoney($this->postbackConfig->roi_type);
        $roi  = round($item->money/$cost*100, 2);
        if ($roi >= $roi_per) {
            $notify->status = LogAdPostback::STATUS_MONEY_ROI_OK;
            return $notify;
        }

        $notify->callAmonut = round(($roi_per-$roi)*$cost/100, 2);

        return $notify;
    }

    // 是否要继续回传金额
    public function getRoiPostMoneyKeep(): AdPayPostbackStruct
    {
        $notify = new AdPayPostbackStruct();
        $notify->roiokMoneyLock = false;

        $roiok_keep = $this->postbackConfig->roiok_keep;
        if ($roiok_keep != 1) {
            $notify->status = LogAdPostback::STATUS_ROIOK_KEEP_FALSE;
            return $notify;
        }

        $roiok_delay = $this->postbackConfig->roiok_delay;
        $howlong = (time() - $this->order->create_time)/60;
        if ($roiok_delay > $howlong) {
            $notify->status = LogAdPostback::STATUS_ROIOK_KEEP_DELAY;
            return $notify;
        }

        // 固定回传金额
        if ($this->postbackConfig->roiok_money_type == AdPostbackConfig::POST_ROIOK_MONEY_LOCK) {
            $notify->roiokMoney = $this->postbackConfig->roiok_money;
            $notify->roiokMoneyLock = true;
        }

        return $notify;
    }

    // 获取当天 付费用户(新) 已回传用户数
    private function getTodayRegPayNum($type)
    {
        $stime = strtotime(date('Y-m-d', $this->order->create_time));
        $etime = $stime+86400;
        $plan_id = $this->plan->id;

        // 粒度
        $wheres = "1=1";
        switch ($type) {
            // 按计划粒度
            case AdPostbackConfig::ROI_TYPE_PLAN:
                break;
            // 按广告组粒度
            case AdPostbackConfig::ROI_TYPE_ADGROUP:
                $val = $this->log->myad_group_id;
                $wheres = "myad_group_id='{$val}'";
                break;
            // 按广告粒度
            case AdPostbackConfig::ROI_TYPE_AD:
                $val = $this->log->myad_ad_id;
                $wheres = "myad_ad_id='{$val}'";
                break;
        }

        $list = LogAdPostbackRegpay::selectRaw("is_post, count(DISTINCT user_id) as num")
            ->whereRaw("convert_time >= {$stime} AND convert_time < {$etime}")
            ->whereRaw("plan_id={$plan_id}")
            ->whereRaw($wheres)
            ->groupBy('is_post')
            ->get()->toArray();

        $alluser = 0;
        $postuser = 0;
        foreach ($list as $val) {
            $alluser += $val['num'];
            if ($val['is_post'] == LogAdPostbackRegpay::POST_STATUS_OK) {
                $postuser += $val['num'];
            }
        }

        return [
            'alluser' => $alluser,
            'postuser' => $postuser,
        ];
    }

    public function checkRegPay(): AdPayPostbackStruct
    {
        $notify = new AdPayPostbackStruct();

        // 不是 付费用户(新) 不用判断
        if (!$this->orderIsRegPay()) {
            return $notify;
        }

        // 判断此用户是否已经在记录里
        $item = AdPostbackRegpayService::getInstance()->getOne($this->log->user_id);
        if ($item) {
            if ($item->is_post == 1) {
                return $notify;
            } else {
                $notify->status = LogAdPostback::STATUS_REG_PAY_PER_DIS;
                return $notify;
            }
        } else {
            // 先把此用户记录，并默认为回传
            $item = AdPostbackRegpayService::getInstance()->add($this->log, LogAdPostbackRegpay::POST_STATUS_OK);
        }

        if (!$this->postbackConfig) {
            return $notify;
        }

        // 不需要控制
        $regpay_per = $this->postbackConfig->regpay_per ?? 100;
        if ($regpay_per == '0' || $regpay_per == 100) {
            return $notify;
        }

        // 获取已控制的人数
        $ret = $this->getTodayRegPayNum($this->postbackConfig->regpay_type);
        $per = 0;
        $alluser = $ret['alluser']-1;
        $postuser = $ret['postuser']-1;
        if ($alluser > 0) {
            $per = round($postuser/$alluser*100, 2);
        }
        if ($per >= $regpay_per) {
            AdPostbackRegpayService::getInstance()->updateStatus($item, LogAdPostbackRegpay::POST_STATUS_DIS);
            $notify->status = LogAdPostback::STATUS_REG_PAY_PER_OK;
            return $notify;
        }

        return $notify;
    }
}
